/*
 * =====================================================================================
 *       Filename:  net.c
 * =====================================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include "net.h"

#define MAXEVENTS 64

int createSocketAndBind(const char* host, const char* port)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s, sfd;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;/* TCP socket */
    hints.ai_flags = AI_PASSIVE;/* All interfaces */

    s = getaddrinfo(host, port, &hints, &result);
    if (s != 0)
    {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        return -1;
    }
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;
        int option = 1;
        setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
        s = bind(sfd, rp->ai_addr, rp->ai_addrlen);
        if (s == 0)
        {
            /* We managed to bind successfully! */
            break;
        }
        close(sfd);
    }

    if (rp == NULL)
    {
        fprintf(stderr, "Could not bind\n");
        return -1;
    }
    freeaddrinfo(result);
    return sfd;
}

int setNonBlock(int sfd)
{
    int flags, s;
    flags = fcntl(sfd, F_GETFL, 0);
    if (flags == -1)
    {
        perror("fcntl");
        return-1;
    }

    flags |= O_NONBLOCK;
    s = fcntl(sfd, F_SETFL, flags);
    if (s ==-1)
    {
        perror("fcntl");
        return-1;
    }
    return 0;
}

int tcpListen(const char* host, const char* port, tcpHandler* handler)
{
    int sfd, s;
    int efd;
    struct epoll_event event;
    struct epoll_event* events;

    sfd = createSocketAndBind(host, port);

    setNonBlock(sfd);
    listen(sfd, SOMAXCONN);

    efd = epoll_create1(0);

    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);

    /* Buffer where events are returned */
    events = calloc(MAXEVENTS, sizeof event);

    /* The event loop */
    while(1)
    {
        int n, i;
        n = epoll_wait(efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++)
        {
            if ((events[i].events & EPOLLERR) ||
                (events[i].events & EPOLLHUP) ||
                !(events[i].events & EPOLLIN))
            {
                /* An error has occured on this fd, or the socket is not
                   ready for reading (why were we notified then?) */
                fprintf(stderr,"epoll error\n");
                close(events[i].data.fd);
            }
            else if (sfd == events[i].data.fd)
            {
                /* We have a notification on the listening socket, which
                   means one or more incoming connections. */
                while(1)
                {
                    struct sockaddr in_addr;
                    socklen_t in_len;
                    int infd;
                    in_len = sizeof(in_addr);
                    infd = accept(sfd, &in_addr, &in_len);
                    if (infd == -1)
                    {
                        if (errno == EAGAIN)
                        {
                            /* We have processed all incoming connections. */
                            break;
                        }
                    }

                    /* Make the incoming socket non-blocking and add it to the list of fds to monitor. */
                    setNonBlock(infd);
                    event.data.fd = infd;
                    event.events = EPOLLIN | EPOLLET;
                    epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
                }
                continue;
            }
            else
            {
                handler(events[i].data.fd);
                //epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
            }
        } // end of for each events
    }

    free(events);
    close(sfd);
    
    return EXIT_SUCCESS;
}
